using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.Schema;
using System.Reflection;
using System.Collections;


namespace CallFlowDesigner
{
    class EmbeddedXmlFileResolver : System.Xml.XmlResolver
    {
        //all resources are local and embedded in the assembly; no need to go over the wire, so no credentials required
        public override System.Net.ICredentials Credentials
        {
            set { throw new NotImplementedException(); }
        }

        public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
        {
            string file = absoluteUri.AbsolutePath.Substring(absoluteUri.AbsolutePath.LastIndexOf('/') + 1); // Yes, it's a hack
            Assembly a = Assembly.GetExecutingAssembly();
            return a.GetManifestResourceStream(a.ManifestModule.Name.Replace(".exe", ".Schema.") + file);
        }
    }

    internal class CallFlowValidator
    {
        private bool valid = true;

        XmlReaderSettings settings = new XmlReaderSettings();

		  public List<string> ValidElements = new List<string>();
		  public List<string> ValidAttributes = new List<string>();

		 public string validationError;

        public CallFlowValidator()
        {
            //Create a reader settings, add the schema, set for 
            //schema validation and add a validation event handler
            settings.ValidationFlags |= XmlSchemaValidationFlags.ProcessSchemaLocation;
            settings.ValidationType = ValidationType.Schema;
			   settings.CloseInput = true;
            settings.IgnoreComments = true;

            XmlSchemaSet xs = new XmlSchemaSet();
            xs.XmlResolver = new EmbeddedXmlFileResolver();
            xs.Add(null, "callflow.xsd");
            xs.Compile();
            settings.Schemas.Add(xs);

				PullOutValidElementsAndAttributes(xs);
        }

		  

        public bool Validate(string xml)
        {
            valid = true;

            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
            {
                try
                {
                    //Attempt to load the XML
						 
                    XmlDocument xdoc = new XmlDocument();
                    XmlReader reader = XmlReader.Create(ms, settings);
                    xdoc.Load(reader);

						  System.Collections.Generic.List<String> ids = new List<string>();
						  
						  XmlNodeList nl = xdoc.SelectNodes("//@id");
						  foreach (XmlAttribute a in nl)
						  {
							  if (ids.Contains(a.Value))
							  {
								  throw new Exception("There are duplicate elements with an id of " + a.Value);

							  }
							  ids.Add(a.Value);
						  }
                }
					 catch (Exception ex)
					 {						 
						 validationError = ex.Message;

						 //the Line and position detail returned in XML Validation errors is misleading
						 if (validationError.Contains(". Line"))
						 {
							 validationError = validationError.Substring(0, validationError.IndexOf(". Line")+1);
						 }
						 valid = false;
					 }
            }
			   return valid;
        }
		  private void PullOutValidElementsAndAttributes(XmlSchemaSet ss)
		  {
			  //not sure why these two aren't being picked up. dirty hack.
			  ValidAttributes.Add("onCallflowCompletedNotification");
			  ValidAttributes.Add("xmlns");

			  foreach (XmlSchema schema in ss.Schemas())
			  {
				  foreach (XmlSchemaType type in schema.SchemaTypes.Values)
				  {
					  if (type is XmlSchemaComplexType)
					  {
						  XmlSchemaComplexType ct = type as XmlSchemaComplexType;
						  WalkTheParticle(ct.ContentTypeParticle);

						  GetAttributes(ct);
					  }
				  }

				  foreach (XmlSchemaElement el in schema.Elements.Values)
				  {
					  WalkTheParticle(el);
				  }
			  }
		  }

		  private void GetAttributes(XmlSchemaComplexType ct)
		  {
			  IDictionaryEnumerator ide = ct.AttributeUses.GetEnumerator();

			  while (ide.MoveNext())
			  {
				  XmlSchemaAttribute a = (XmlSchemaAttribute)ide.Value;
				  if (a.Name != null && !ValidAttributes.Contains(a.Name))
				  {
					  ValidAttributes.Add(a.Name);
					  Console.WriteLine("Attribute - " + a.Name);
				  }
			  }
		  }


		  private void WalkTheParticle(XmlSchemaParticle particle)
		  {
			  if (particle is XmlSchemaElement)
			  {
				  XmlSchemaElement elem = particle as XmlSchemaElement;

				  if (elem.Name != null && elem.Name.Length > 0 && !ValidElements.Contains(elem.Name) && elem.MaxOccurs > 0)
				  {
					  ValidElements.Add(elem.Name);
					  Console.WriteLine("Element - " + elem.Name);
				  }

				  if (elem.RefName.IsEmpty)
				  {
					  XmlSchemaType type = (XmlSchemaType)elem.ElementSchemaType;

					  if (type is XmlSchemaComplexType)
					  {
						  XmlSchemaComplexType ct = type as XmlSchemaComplexType;
						  if (ct.QualifiedName.IsEmpty)
						  {
							  WalkTheParticle(ct.ContentTypeParticle);
						  }
					  }
				  }
			  }

			  else if (particle is XmlSchemaGroupBase)

			  //xs:all, xs:choice, xs:sequence
			  {
				  XmlSchemaGroupBase baseParticle = particle as XmlSchemaGroupBase;
				  foreach (XmlSchemaParticle subParticle in baseParticle.Items)
				  {
					  WalkTheParticle(subParticle);
				  }
			  }
		  }

    }
}
